<?php

declare(strict_types=1);

namespace Core\Basics;

use JetBrains\PhpStorm\ExpectedValues;
use Zms\Unit\Arr;
use Zms\HyperfCasts\{
    StrtotimeCasts, ArrjoinCasts, JsonCasts, BoolCasts, PointCasts
};
use Zms\Exception\AppException;
use Zms\Unit\Str;
use Core\HyperfUnit\Callback;
use Hyperf\Database\ConnectionInterface;
use Hyperf\Database\Model\Builder;
use Hyperf\Database\Model\Model;
use Hyperf\Di\Annotation\Inject;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;

abstract class BasicsServe
{
    protected LoggerInterface $logger;

    #[Inject]
    protected CacheInterface $cache;


    public function __construct()
    {
        /**
         * 注入日志服务
         */
        $this->logger = logger('serve', Str::getClassName(static::class));
    }

    /**
     * 判断数据是否存在
     * @param string|array|null $where
     * @param bool|array $filter
     * @return bool
     */
    public function exists(string|array|null $where = null, bool|array $filter = true): bool
    {
        return $this->search($where, $filter)->exists();
    }

    /**
     * 创建搜索引擎
     * @param string|array|null $where
     * @param bool|array $filter 当$where为字符串时$filter为数组,绑定数据  当$where为数组时$filter为布尔值,是否只接收数据库的值
     * @param string $range data默认数据 recycle回收站 all所有数据
     * @return Builder
     */
    public function search(
        string|array|null                                    $where = null,
        bool|array                                           $filter = true,
        #[ExpectedValues(['data', 'recycle', 'all'])] string $range = 'data'
    ): Builder
    {
        //获取模型方法
        $model_methods = get_class_methods(static::model());
        /**
         * @var Builder $query
         */
        if (in_array('getDeletedAtColumn', $model_methods)) {//配置了软删除
            if ($range === 'all') {
                $query = static::model()::withTrashed(true);
            } elseif ($range === 'recycle') {
                $query = static::model()::onlyTrashed();
            } else {
                $query = static::query();
            }
        } else {
            $query = static::query();
        }

        //没有查询条件,返回原始对象
        if (is_null($where)) {
            return $query;
        }
        //查询条件为字符串,直接返回
        if (is_string($where)) {
            $query->whereRaw($where, (is_bool($filter)) ? [] : $filter);
        }
        //数组条件
        if (is_array($where) && count($where) > 0) {
            $methods = get_class_methods($this);
            $fields = $this->fields();
            foreach ($where as $key => $value) {
                $queryNmae = Str::doubleToHump('search_' . $key);
                if (in_array($queryNmae, $methods)) {
                    call_user_func([$this, $queryNmae], $query, $value);
                    continue;
                }
                if (!(in_array($key, $fields) || $filter === false)) {
                    continue;
                }
                if (is_array($value) && count($value) > 0) {
                    $query->whereIn($key, $value);
                } elseif (!(is_null($value) || (is_string($value) && strlen($value) === 0))) {
                    $query->where($key, $value);
                }
            }
        }
        return $query;
    }

    /**
     * 关联模型
     * @return BasicsModel::class
     */
    abstract public static function model(): string;

    /**
     * 查询构造器
     * @return Builder
     */
    public function onlyTrashed(): Builder
    {
        return static::model()::onlyTrashed();
    }

    /**
     * 查询构造器
     * @return Builder
     */
    public function query(): Builder
    {
        return static::model()::query();
    }

    /**
     * 获取数据表所有字段
     */
    public function fields(): array
    {
        return Callback::cache(static::class . __METHOD__, function () {
            return static::model()::fields();
        });
    }

    /**
     * 获取数据条数
     * @param string|array|null $where
     * @param bool|array $filter
     * @return int
     */
    public function count(string|array|null $where = null, bool|array $filter = true): int
    {
        return $this->search($where, $filter)->count();
    }

    /**
     * 通过主键获取数据
     * @param $primary
     * @return Builder|Model|null
     */
    public function primaryByFirst($primary): Builder|Model|null
    {
        if (empty($primary)) {
            return null;
        }
        return $this->search([$this->primaryKey() => $primary])->first();
    }

    /**
     * 获取表主键
     * @return string
     */
    public function primaryKey(): string
    {
        return Callback::cache(static::class . __METHOD__, function () {
            return static::model()::primaryKey();
        });
    }

    /**
     * 快捷插入数据
     * @param array $values
     * @return BasicsModel|false
     */
    public function created(array $values): BasicsModel|false
    {
        $model = $this->getModel();
        $model->setAttributes($values);
        if ($model->save()) {
            return $model;
        } else {
            return false;
        }
    }

    /**
     * 获取模型
     * @return BasicsModel|Model
     */
    public function getModel(): Model|BasicsModel
    {
        return static::model()::getModel();
    }

    /**
     * 更新数据
     * @param array $data
     * @param array|null|string $where
     * @param bool $returnModel
     * @return bool|BasicsModel
     * @throws AppException
     */
    public function updated(array $data, null|array|string $where = null, bool $returnModel = false): bool|BasicsModel
    {
        /**
         * @var BasicsModel $model
         */
        if (is_null($where)) {
            $primary = $this->primaryKey();
            if (isset($data[$primary])) {
                if (empty($data[$primary])) throw new AppException('缺少数据更新条件');
                $model = $this->search([$primary => $data[$primary]])->first();
            } else {
                throw new AppException('缺少数据更新条件');
            }
        } else {
            if (empty($where)) throw new AppException('缺少数据更新条件');
            $model = $this->search($where)->first();
        }
        if (isset($model) && $model) {
            if ($returnModel) {
                return $model->setAttributes($data)->save() ? $model : false;
            }
            return $model->setAttributes($data)->save();
        } else {
            return false;
        }
    }

    /**
     * 插入数据,数据有则更新,没有则插入
     * @param array $data
     * @return bool
     */
    public function conserve(array $data): bool
    {
        return static::model()::conserve($data);
    }

    /**
     * 获取数据表名称
     * @return string
     */
    public function tableName(): string
    {
        return Callback::cache(static::class . __METHOD__, function () {
            return static::model()::tableName();
        });
    }

    /**
     * 获取数据库连接名称
     * @return mixed
     */
    public function connectionName(): mixed
    {
        return Callback::cache(static::class . __METHOD__, function () {
            return static::model()::connectionName();
        });
    }

    /**
     * 获取数据库的接口属性注释
     * @param array $attr
     * @param array $deflabel
     * @return array
     */
    public function apinote(array $attr = ['*'], array $deflabel = []): array
    {
        $key = Arr::md5([static::class, $attr, $deflabel], true);
        return Callback::cache($key, function () use ($attr, $deflabel) {
            $labels = $this->attributeLabels();
            $note = [];
            foreach ($labels as $key => $label) {
                if (in_array('*', $attr) || in_array($key, $attr)) {
                    $type = $deflabel[$key] ?? $this->getAttributeType($key);
                    $note[$key] = "{$type} {$label}";
                }
            }
            return $note;
        });
    }

    /**
     * 获取属性名称
     */
    public function attributeLabels()
    {
        return Callback::cache(static::class . __METHOD__, function () {
            return static::model()::attributeLabels();
        });
    }

    /**
     * @param $key
     * 获取字段类型
     * @return string
     */
    public function getAttributeType($key): string
    {
        $casts = $this->getModel()->getCasts();

        $type = $casts[$key] ?? 'string';
        return match ($type) {
            'int', 'integer', StrtotimeCasts::class => 'int',
            'float', 'real' => 'float',
            'double' => 'double',
            'decimal' => 'decimal',
            'boolean', 'bool', BoolCasts::class => 'bool',
            ArrjoinCasts::class, JsonCasts::class => 'array',
            'json', 'object', PointCasts::class => 'json',
            default => 'string',
        };
    }

    /**
     * 指定字段取反
     */
    public function getReverseFields(array $keys): array
    {
        $finds = [];
        foreach ($this->fields() as $key) {
            if (in_array($key, $keys)) {
                continue;
            }
            $finds[] = $key;
        }
        return $finds;
    }

    /**
     * @describe 判断字段是否存在
     * @user zlf <zms-pro@qq.com> 2025/3/11
     * @param string $field
     * @return bool
     */
    public function hasField(string $field): bool
    {
        return static::model()::hasField($field);
    }


    /**
     * 获取一个连接
     * @return ConnectionInterface
     */
    public function connection(): ConnectionInterface
    {
        return static::model()::connection();
    }


    /**
     * 通过获取关联数据
     * @param array $data 需要处理的数据
     * @param string $join_key 用于关联的字段
     * @param array{label?:string,value?:string,desc?:string} $option
     *              -label  关联数据用于描述的字段
     *              -value  关联数据字段
     *              -desc   新的描述字段名称
     * @return array
     */
    public function setJoinData(array $data, string $join_key, array $option = []): array
    {
        return static::model()::setJoinData($data, $join_key, $option);
    }


    /**
     * 通过获取关联数据
     * @param string|int|array $val
     * @param array{label?:string,value?:string,desc?:string} $option
     *              -label  关联数据用于描述的字段
     *              -value  关联数据字段
     *              -desc   新的描述字段名称
     * @return array|string
     */
    public function getJoinValue(string|int|array $val, array $option = []): array|string
    {
        return static::model()::getJoinValue($val, $option);
    }
}
